module Assign2(CLOCK_50,KEY, SW, HEX0,HEX1,HEX2,HEX3);
	input [3:0] KEY;
	input [9:0] SW;
	input CLOCK_50;// clk;
	//debug wire CLOCK_50 = ! KEY[0];
	output [6:0] HEX0, HEX1, HEX2, HEX3;
	parameter DBITS=32;
	parameter BUSZ=32'hzzzzzzzz;
	tri [DBITS-1:0] mybus;
	
	//PLL SET TO 75 mhz
	wire clk;
	wire locked;
	
	PLLAssign2	PLLAssign2_inst (
		.inclk0 ( CLOCK_50 ),
		.c0 ( clk ),
		.locked ( locked )
	);

	
	//MEMORY
	
	// Create the memory unit and connect to the bus
	reg [(DBITS-1):0] MAR; // MAR register
	reg LdMAR,WrMem,DrMem; // Control signals
	wire [(DBITS-1):0] memin=mybus;
	// Connect MAR input to the bus
	always @(posedge clk)
		if(LdMAR)
			MAR <= mybus;
			
	reg [15:0] HexOut;
	dispHex ss3(HEX3,HexOut[15:12]);
	dispHex ss2(HEX2,HexOut[11:8]);
	dispHex ss1(HEX1,HexOut[7:4]);
	dispHex ss0(HEX0,HexOut[3:0]);
	
	always @(posedge clk)
	  if(WrMem&&(MAR==32'hFFFFF800))
		HexOut <= memin[15:0];
		
	wire [(DBITS-1):0] MemVal;
	// Connect memory array to other signals
	wire MemEnable=(MAR[(DBITS-1):14]==18'b0);
		
	//wire [(DBITS-1):0] memout;
	// Connect memory array to other signals
	
	MEM #(.DBITS(DBITS),.ABITS(11),.MFILE("Assign2.mif")) memory(
		.ADDR(MAR[13:2]),
		.DIN(memin),
		.DOUT(MemVal),.WE(WrMem),.CLK(clk));
		
	wire [(DBITS-1):0] memout=
		MemEnable?MemVal:
		(MAR==32'hFFFFF000)?{28'b0,KEY}:
		(MAR==32'hFFFFF004)?{22'b0,SW}:
					32'hDEADBEEF;
	// Connect memory array output to bus (controlled by DrMem)
	assign mybus=DrMem?memout:BUSZ;
	
	
	//END MEMORY
	
	//REGISTERS
	
	//Create registers and controls for connecting to the bus
	reg WrReg, DrReg; 
	reg [3:0] regno;
	wire [(DBITS-1):0] regout;
	REGFILE registers(
		.REGNO(regno),
		.DIN(mybus),
		.DOUT(regout),.WE(WrReg),.CLK(clk));
	// Connect register array output to bus (controlled by DrReg)
	assign mybus=DrReg?regout:BUSZ;
	
	
	
	//END REGISTERS
	
	
	
	//START ALU
	
	// Create A and B and connect to the ALU
	reg [(DBITS-1):0] A, B;
	reg LdA, LdB, DrALU;
	reg [4:0] func;
		always @(posedge clk) begin
			if(LdA)
				A <= mybus;
			else if(LdB)
				B <= mybus;
		end
	wire [(DBITS-1):0] aluout;	
	
	ALU #(.BITS(32)) aluTest(.A(A),.B(B),.CTL(func),.OUT(aluout));
		
	wire [0:0] cond=aluout[0:0];
	assign mybus=DrALU?aluout:BUSZ;
	
	//END ALU
	
	
	//PC
	
	// Create PC and connect it to the bus
	reg [(DBITS-1):0] PC=32'h00000400; // Initial value
	reg LdPC, DrPC, IncPC;
		always @(posedge clk) begin
			if(LdPC)
				PC <= mybus;
			else if(IncPC)
				PC <= PC + 4;
		end
	assign mybus=DrPC?PC:BUSZ;
	
	
	//END PC
	
	
	//IR
	
	// Create A and B and connect to the ALU
	reg [(DBITS-1):0] IR;
	reg LdIR, DrOff, ShOff, DrZero;
		always @(posedge clk) begin
			if(LdIR)
				IR <= mybus;
		end
	wire [DBITS-1:0] irout;
	wire [DBITS-1:0] shout;
	
	//sign extend the offset
	signExtend #(.IBITS(16),.OBITS(32)) extendOffset(IR[15:0],irout);

	assign shout = irout << 2;

	assign mybus=(ShOff) ? shout:
				 (DrOff) ? irout:
				 (DrZero) ? irout << 32:
					BUSZ;
	
	
	
	
	//END IR
	
	
	//CONTROL
	
	//simply use a counter to make identifying each state inside switch statement much easier
	parameter S_BITS=5;
	parameter [(S_BITS-1):0]
	S_FETCH1=0,
	S_FETCH2=S_FETCH1+1'b1,
	S_FETCH3=S_FETCH2+1'b1,
	S_FETCH4=S_FETCH3+1'b1,
	
	S_ALU1 =S_FETCH4+1'b1,
	S_ALU2 =S_ALU1+1'b1,
	S_ALU3 =S_ALU2+1'b1,
	
	S_JALR1=S_ALU3+1'b1,
	S_JALR2=S_JALR1+1'b1,
	S_JALR3=S_JALR2+1'b1,
	S_JALR4=S_JALR3+1'b1,
	
	S_LW1=S_JALR4+1'b1,
	S_LW2=S_LW1+1'b1,
	S_LW3=S_LW2+1'b1,
	S_LW4=S_LW3+1'b1,
	S_LW5=S_LW4+1'b1,
	
	S_SW1=S_LW5+1'b1,
	S_SW2=S_SW1+1'b1,
	S_SW3=S_SW2+1'b1,
	S_SW4=S_SW3+1'b1,
	
	S_BEQ1=S_SW4+1'b1,
	S_BEQ2=S_BEQ1+1'b1,
	S_BEQ3=S_BEQ2+1'b1,
	S_BEQ4=S_BEQ3+1'b1,
	S_BEQ5=S_BEQ4+1'b1,
	S_BEQ6=S_BEQ5+1'b1;

	
	reg [(S_BITS-1):0] state=S_FETCH1,next_state;
	
	wire [7:0] opcode = IR[DBITS-1:DBITS-8];
	wire [3:0] rdst = IR[DBITS-9:DBITS-12];
	wire [3:0] rsrc1 = IR[DBITS-13:DBITS-16];
	wire [3:0] rsrc2 = IR[DBITS-17:DBITS-20];
	
	wire [15:0] aluim=IR[DBITS-17:DBITS-32];
	
	wire [4:0] alufn=opcode[5:1];
	
	
	parameter CMD_ADD = 16;
	//once instruction is complete make sure to return to fetch a new instruction via fetch1
	always @(state or opcode or rsrc1 or rsrc2 or rdst or aluim or alufn or cond) begin
		{DrZero, ShOff,IncPC,LdMAR,WrMem,DrMem, LdA,LdB,DrALU,LdPC,DrPC,LdIR,DrOff,DrReg,WrReg,regno, func,next_state}=
		{1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0,1'b0,1'b0, 1'b0,1'b0,1'b0,1'b0, 1'b0, 1'b0, 1'b0,4'b0,5'd16,state+1'd1};
		case(state)
			S_FETCH1: {DrPC,LdMAR}={1'b1,1'b1};
			S_FETCH2: IncPC=1'b1; // Memory is being read here extra cycle for reads
			S_FETCH3: {DrMem,LdIR}={1'b1,1'b1};
			S_FETCH4: begin //essentially dispatch
				if(opcode[7:6]==2'b00)
					next_state=S_ALU1;
				else if(opcode[7:6]==2'b01) begin
					if(opcode[5:0]==6'b000000)
						next_state=S_LW1;
					else //if (opcode[5:0]==6'b100000)
						next_state=S_SW1;
				end
				else if(opcode[7:6]==2'b10) begin
					if(opcode[5:0]==6'b000000)
						next_state=S_JALR1;
					else //if (opcode[5:4]==2'b01)
						next_state=S_BEQ1;
				end
			end
			
			S_ALU1: {DrReg, LdA, regno} = {1'b1, 1'b1, rsrc1};
			//have to decide if register or immediate alu op, odd means immediate co check last bit
			S_ALU2: begin
				if (opcode[0:0]==1'b1)
					{DrOff, LdB} = {1'b1, 1'b1};
				else
					{DrReg, LdB, regno} = {1'b1, 1'b1, rsrc2};
			end
			S_ALU3: begin
				{WrReg, DrALU, regno, func} = {1'b1, 1'b1, rdst, alufn};
				next_state=S_FETCH1;
			end
			
			S_JALR1: {DrPC, WrReg, regno} = {1'b1, 1'b1, rdst};
			S_JALR2: {DrReg, LdA, regno} = {1'b1, 1'b1, rsrc1};
			S_JALR3: begin
				{ShOff, LdB} = {1'b1, 1'b1};
			end
			S_JALR4: begin
				{DrALU, LdPC} = {1'b1, 1'b1};
				next_state=S_FETCH1;
			end
			
			//account for extra cycle for memory reads 
			//rdst = mem[rsrc + sxt(imm)]
			S_LW1: begin
				{DrReg, LdA, regno} = {1'b1, 1'b1, rsrc1};
			end
			S_LW2: begin
				{DrOff, LdB} = {1'b1, 1'b1};
			end
			S_LW3: begin
				{DrALU,LdMAR, func}={1'b1, 1'b1, 5'd16};
			end
			S_LW4: begin
				//extra cycle here for memory reads sucks to waste a cycle
			end
			S_LW5: begin
				{DrMem, WrReg, regno} = {1'b1, 1'b1, rdst};
				next_state=S_FETCH1;
			end
			
			//mem[rsrc1 + sxt(imm)] = rdst
			S_SW1: begin
				{DrReg, LdA, regno} = {1'b1, 1'b1, rsrc1};
			end
			S_SW2: begin
				{DrOff, LdB}={1'b1, 1'b1};
			end
			S_SW3: begin
				{DrALU, LdMAR} = {1'b1, 1'b1};
			end		
			S_SW4: begin
				{DrReg, WrMem, regno} = {1'b1, 1'b1, rdst};
				next_state=S_FETCH1;
			end
			S_BEQ1: begin
				{DrReg, LdA, regno} = {1'b1, 1'b1, rdst};
			end
			S_BEQ2: begin
				//check if zero or a register
				if(opcode[0:0]==1'b1)
					{LdB, DrZero} = {1'b1, 1'b1};
				else
					{DrReg, LdB, regno} = {1'b1, 1'b1, rsrc1};
			end
			S_BEQ3: begin
				{DrALU, func} = {1'b1, alufn};
				if(opcode[1:0]==2'b00) // BEQ
					if(cond)
						next_state=S_BEQ4;
					else
						next_state=S_FETCH1;
				else if(opcode[1:0]==2'b01) // BEQZ
					if(cond)
						next_state=S_BEQ4;
					else
						next_state=S_FETCH1;
				else if(opcode[1:0]==2'b10) // BNE
					if(cond)
						next_state=S_BEQ4;
					else
						next_state=S_FETCH1;
				else if(opcode[1:0]==2'b11) // BNEZ
					if(cond)
						next_state=S_BEQ4;
					else
						next_state=S_FETCH1;
			end
			S_BEQ4: begin
				{DrPC, LdA}={1'b1, 1'b1};
			end
			S_BEQ5: begin
				{ShOff, LdB}={1'b1, 1'b1};
			end
			S_BEQ6: begin
				{DrALU, LdPC, func} = {1'b1, 1'b1, 5'd16};
				next_state=S_FETCH1;
			end
			default:;
		endcase
	end
	always @(posedge clk)
		if(locked)
			state<=next_state;	
endmodule




module ALU(A,B,CTL,OUT);
	parameter BITS;
	parameter CMD_AND = 0;
	parameter CMD_NAND = 1;
	parameter CMD_OR = 2;
	parameter CMD_NOR = 3;
	parameter CMD_XOR = 4;
	parameter CMD_EQ = 8;
	parameter CMD_NE = 9;
	parameter CMD_LT = 10;
	parameter CMD_LE = 11;
	parameter CMD_ADD = 16;
	parameter CMD_SUB = 17;
	parameter CBITS = 5;
	input [(CBITS-1):0] CTL;
	input [(BITS-1):0] A,B;
	output [(BITS-1):0] OUT;
	wire signed [(BITS-1):0] A,B;
	reg signed [(BITS-1):0] tmpout;
	always @(A or B or CTL) begin
		case(CTL)
			CMD_ADD: tmpout = A+B;
			CMD_SUB: tmpout = A-B;
			CMD_AND: tmpout = A&B;
			CMD_NAND: tmpout = ~(A&B);
			CMD_OR: tmpout = A|B;
			CMD_XOR: tmpout = A^B;
			CMD_NOR: tmpout = ~(A|B);
			CMD_EQ: tmpout = (A==B);
			CMD_NE: tmpout = (A!=B);
			CMD_LT:  tmpout = (A<B);
			CMD_LE: tmpout = (A<=B);
			default: tmpout = 1'bX;
		endcase
	end
	assign OUT=tmpout;
endmodule


module MEM(ADDR,DIN,DOUT,WE,CLK);
	parameter DBITS; // Number of data bits
	parameter ABITS; // Number of address bits
	parameter WORDS = (1<<ABITS);
	parameter MFILE = ""; // File name passed as a parameter
	(* ram_init_file = MFILE *)
	reg [(DBITS-1):0] mem[(WORDS-1):0];
	input [(ABITS-1):0] ADDR;
	input [(DBITS-1):0] DIN;
	output reg [(DBITS-1):0] DOUT;
	input CLK,WE;
	always @(posedge CLK) begin
		if(WE) begin 
			mem[ADDR] <= DIN;
		end
		DOUT <= mem[ADDR];
	end
endmodule

//very simular to memory
module REGFILE(REGNO,DIN,DOUT,WE,CLK);
	parameter DBITS= 32; // Number of data bits
	parameter ABITS= 4; // Number of address bits
	parameter WORDS = 1<<ABITS;
	//parameter MFILE = ""; // File name passed as a parameter
	//(* ram_init_file = MFILE *)
	reg [(DBITS-1):0] regs[(WORDS-1):0];
	input [(ABITS-1):0] REGNO;
	input [(DBITS-1):0] DIN;
	output [(DBITS-1):0] DOUT;
	input CLK,WE;
	always @(posedge CLK) begin
		if(WE)
			regs[REGNO] <= DIN;
	end
	assign DOUT=regs[REGNO];
endmodule



module signExtend(IN,OUT);
	parameter IBITS;
	parameter OBITS;
	input [(IBITS-1):0] IN;
	output [(OBITS-1):0] OUT;
	assign OUT={{(OBITS-IBITS){IN[IBITS-1]}},IN};
endmodule

module dispHex(sseg, num);
	output[6:0] sseg;
	input [7:0] num;
	assign sseg=
		(num == 4'd0) ? 7'b1000000 :
		(num == 4'd1) ? 7'b1111001 :
		(num == 4'd2) ? 7'b0100100 :
		(num == 4'd3) ? 7'b0110000 :
		(num == 4'd4) ? 7'b0011001 :
		(num == 4'd5) ? 7'b0010010 :
		(num == 4'd6) ? 7'b0000010 :
		(num == 4'd7) ? 7'b1111000 :
		(num == 4'd8) ? 7'b0000000 :
		(num == 4'd9) ? 7'b0010000 :
		(num == 4'd10) ? 7'b0001000 :
		(num == 4'd11) ? 7'b0000011 :
		(num == 4'd12) ? 7'b1000110 :
		(num == 4'd13) ? 7'b0100001 :
		(num == 4'd14) ? 7'b0000110 :
		(num == 4'd15) ? 7'b0001110 :
					    7'b0110000 ;
					    
endmodule

